import { document } from "./browser";
import { isFn, noop, options } from "./util";

var globalEvents = {};
export var eventPropHooks = {}; //用于在事件回调里对事件对象进行
export var eventHooks = {}; //用于在元素上绑定特定的事件
//根据onXXX得到其全小写的事件名, onClick --> click, onClickCapture --> click,
// onMouseMove --> mousemove

export var eventLowerCache = {
  onClick: "click",
  onChange: "change",
  onWheel: "wheel"
};
/**
 * 判定否为与事件相关
 *
 * @param {any} name
 * @returns
 */
export function isEventName(name) {
  return /^on[A-Z]/.test(name);
}

export var isTouch = "ontouchstart" in document;

export function dispatchEvent(e, type, end) {
  //__type__ 在injectTapEventPlugin里用到
  e = new SyntheticEvent(e);
  if (type) {
    e.type = type;
  }
  var bubble = e.type;

  var hook = eventPropHooks[bubble];
  if (hook && false === hook(e)) {
    return;
  }

  var paths = collectPaths(e.target, end || document);
  var captured = bubble + "capture";
  options.async = true;
  triggerEventFlow(paths, captured, e);

  if (!e._stopPropagation) {
    triggerEventFlow(paths.reverse(), bubble, e);
  }
  options.async = false;
  options.flushBatchedUpdates();
}

function collectPaths(from, end) {
  var paths = [];
  do {
    if (from === end) {
      break;
    }
    var events = from.__events;
    if (events) {
      paths.push({ dom: from, events: events });
    }

  } while ((from = from.parentNode) && from.nodeType === 1);
  // target --> parentNode --> body --> html
  return paths;
}

function triggerEventFlow(paths, prop, e) {
  for (var i = paths.length; i--;) {
    var path = paths[i];
    var fn = path.events[prop];
    if (isFn(fn)) {
      e.currentTarget = path.dom;
      fn.call(path.dom, e);
      if (e._stopPropagation) {
        break;
      }
    }
  }
}

export function addGlobalEvent(name) {
  if (!globalEvents[name]) {
    globalEvents[name] = true;
    addEvent(document, name, dispatchEvent);
  }
}

export function addEvent(el, type, fn, bool) {
  if (el.addEventListener) {
    // Unable to preventDefault inside passive event listener due to target being
    // treated as passive
    el.addEventListener(
      type,
      fn,
      bool || false
    );
  } else if (el.attachEvent) {
    el.attachEvent("on" + type, fn);
  }
}


var rcapture = /Capture$/;
export function getBrowserName(onStr) {
  var lower = eventLowerCache[onStr];
  if (lower) {
    return lower;
  }
  var camel = onStr.slice(2).replace(rcapture, "");
  lower = camel.toLowerCase();
  eventLowerCache[onStr] = lower;
  return lower;
}


eventPropHooks.click = function (e) {
  return !e.target.disabled;
};

/* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
            firefox DOMMouseScroll detail 下3 上-3
            firefox wheel detlaY 下3 上-3
            IE9-11 wheel deltaY 下40 上-40
            chrome wheel deltaY 下100 上-100 */
/* istanbul ignore next  */
const fixWheelType =
    "onmousewheel" in document
      ? "mousewheel"
      : document.onwheel !== void 666 ? "wheel" : "DOMMouseScroll";
const fixWheelDelta =
    fixWheelType === "mousewheel"
      ? "wheelDetla"
      : fixWheelType === "wheel" ? "deltaY" : "detail";
eventHooks.wheel = function (dom) {
  addEvent(dom, fixWheelType, function (e) {
    var delta = e[fixWheelDelta] > 0 ? -120 : 120;
    var deltaY = ~~dom.__wheel + delta;
    dom.__wheel = deltaY;
    e = new SyntheticEvent(e);
    e.type = "wheel";
    e.deltaY = deltaY;
    dispatchEvent(e);
  });
};

var fixFocus = {};
"blur,focus".replace(/\w+/g, function (type) {
  eventHooks[type] = function () {
    if (!fixFocus[type]) {
      fixFocus[type] = true;
      addEvent(
        document,
        type,
        dispatchEvent,
        true
      );
    }
  };
});
/**
 * 
DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值；
对于其他事件，这个属性的值是null。IE不支持realtedTarget属性，但提供了保存着同样信息的不同属性。
在mouseover事件触发时，IE的fromElement属性中保存了相关元素；
在mouseout事件出发时，IE的toElement属性中保存着相关元素。
但fromElement与toElement可能同时都有值
 */
function getRelatedTarget(e) {
  if (!e.timeStamp) {
    e.relatedTarget = e.type === "mouseover" ? e.fromElement : e.toElement;
  }
  return e.relatedTarget;
}

function contains(a, b) {
  if (b) {
    while ((b = b.parentNode)) {
      if (b === a) {
        return true;
      }
    }
  }
  return false;
}

String("mouseenter,mouseleave").replace(/\w+/g, function (type) {
  eventHooks[type] = function (dom, name) {
    var mark = "__" + name;
    if (!dom[mark]) {
      dom[mark] = true;
      var mask = name === "mouseenter" ? "mouseover" : "mouseout";
      addEvent(dom, mask, function (e) {
        let t = getRelatedTarget(e);
        if (!t || (t !== dom && !contains(dom, t))) {
          var common = getLowestCommonAncestor(dom, t);
          //由于不冒泡，因此paths长度为1 
          dispatchEvent(e, name, common);
        }
      });
    }
  };
});

function getLowestCommonAncestor(instA, instB) {
  var depthA = 0;
  for (var tempA = instA; tempA; tempA = tempA.parentNode) {
    depthA++;
  }
  var depthB = 0;
  for (var tempB = instB; tempB; tempB = tempB.parentNode) {
    depthB++;
  }

  // If A is deeper, crawl up.
  while (depthA - depthB > 0) {
    instA = instA.parentNode;
    depthA--;
  }

  // If B is deeper, crawl up.
  while (depthB - depthA > 0) {
    instB = instB.parentNode;
    depthB--;
  }

  // Walk in lockstep until we find a match.
  var depth = depthA;
  while (depth--) {
    if (instA === instB) {
      return instA;
    }
    instA = instA.parentNode;
    instB = instB.parentNode;
  }
  return null;
}


if (isTouch) {
  eventHooks.click = noop;
  eventHooks.clickcapture = noop;
}

export function createHandle(name, fn) {
  return function (e) {
    if (fn && fn(e) === false) {
      return;
    }
    dispatchEvent(e, name);
  };
}

var changeHandle = createHandle("change");
var doubleClickHandle = createHandle("doubleclick");

//react将text,textarea,password元素中的onChange事件当成onInput事件
eventHooks.changecapture = eventHooks.change = function (dom) {
  var mask = /text|password/.test(dom.type) ? "input" : "change";
  addEvent(document, mask, changeHandle);
};

eventHooks.doubleclick = eventHooks.doubleclickcapture = function () {
  addEvent(document, "dblclick", doubleClickHandle);
};

export function SyntheticEvent(event) {
  if (event.nativeEvent) {
    return event;
  }
  for (var i in event) {
    if (!eventProto[i]) {
      this[i] = event[i];
    }
  }
  if (!this.target) {
    this.target = event.srcElement;
  }
  this.fixEvent();
  this.timeStamp = new Date() - 0;
  this.nativeEvent = event;
}

var eventProto = (SyntheticEvent.prototype = {
  fixEvent: function () { }, //留给以后扩展用
  preventDefault: function () {
    var e = this.nativeEvent || {};
    e.returnValue = this.returnValue = false;
    if (e.preventDefault) {
      e.preventDefault();
    }
  },
  fixHooks: function () { },
  stopPropagation: function () {
    var e = this.nativeEvent || {};
    e.cancelBubble = this._stopPropagation = true;
    if (e.stopPropagation) {
      e.stopPropagation();
    }
  },
  persist: noop,
  stopImmediatePropagation: function () {
    this.stopPropagation();
    this.stopImmediate = true;
  },
  toString: function () {
    return "[object Event]";
  }
});
/* istanbul ignore next  */
//freeze_start
Object.freeze ||
    (Object.freeze = function (a) {
      return a;
    });
//freeze_end
